package spass.scene.forms;

import spass.geometry.Point;
import spass.geometry.Ray;
import spass.geometry.Vector;

public class Sphere implements Form {

    private final Point center;
    private final double radius;

    public Sphere(Point center, double radius) {
        this.center = center;
        this.radius = radius;
    }

    public Point getCenter() {
        return center;
    }

    public double getRadius() {
        return radius;
    }

    @Override
    public Vector surfaceNormalAt(Point point) {
        Point difference = point.substract(center);

        return new Vector(difference.getX(), difference.getY(), difference.getZ());
    }

    /**
     * Checks this sphere for intersection with a given ray. Only cares for the
     * closest intersection.
     * 
     * Based on: http://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
     * 
     * @param ray
     *            Ray with direction vector of length 1
     * 
     * @return the coordinates of the closest intersection, or null
     */
    @Override
    public Point firstIntersectionPoint(Ray ray) {
        Point offset = ray.getOrigin();
        Vector direction = ray.getDirection(); // guaranteed unit vector - simplifies the calculation

        Point c = center.substract(offset);
        double lc = direction.dotProduct(c);

        double discriminant = getDiscriminant(c, lc);
        if (discriminant >= 0) { // 1 or 2 intersections occured
            double distance = lc - Math.sqrt(discriminant); // only care for closest intersection
            if (distance > 0) {
                Vector scaledDirection = direction.scalarMultiply(distance);
                Point point = new Point(scaledDirection.getX(), scaledDirection.getY(), scaledDirection.getZ());

                return point.add(offset);
            }
        }

        return null; // no intersection
    }

    private double getDiscriminant(Point c, double lc) {
        double lc2 = lc * lc;
        double c2 = c.dotProduct(c);
        double r2 = radius * radius;

        return lc2 - c2 + r2;
    }

}
